if [ catch {package require Thread }] {
  puts "$argv0 requires tclsh to be compiled with thread support!"
  puts "Try download&install a newer version:"
  puts "wget http://downloads.activestate.com/ActiveTcl/releases/8.6.0.0b6/ActiveTcl8.6.0.0b6.295132-linux-x86_64-threaded.tar.gz"
  exit 
}

# Templates & global variables

# Template for generate input file for APBS dummy run.
set dummy_template {
# FOCUSED POTENTIAL FOR FOCUSED_Huge SYSTEM
elec name focused_$level
mg-dummy
dime $nx $ny $nz
nlev 4
glen $sizex $sizey $sizez
gcent $x0 $y0 $z0
mol 1
lpbe
bcfl zero
[getIonsDef]
pdie $par_diel_solute #solute dielectric constant($solute_protein_pqr)
sdie $par_diel_water #water dielectric constant
chgm spl2
srfm mol
srad 1.4
swin 0.3
sdens 10.0
temp $par_temperature #temperature 
calcenergy total
calcforce no
write dielx dx dielx_$level # spatially dependent diel. constant
write diely dx diely_$level # out using the Conoly/Molecular surface
write dielz dx dielz_$level
write kappa dx kappa_$level # write out the kappa map
write charge dx charge_$level # write out the charge map
end
}

# Template for generate input file for APBS calculation run.
set calc_template {
    elec name state$i
    mg-manual
    dime $nx $ny $nz
    nlev 4
    glen $sizex $sizey $sizez
    gcent $x0 $y0 $z0
    mol 1
    npbe
    bcfl $boundary
    [getIonsDef]
    pdie $par_diel_solute #solute
    sdie $par_diel_water #water dielectric constant
    chgm spl2
    srfm mol
    srad 1.4
    swin 0.3
    sdens 10.0
    temp $par_temperature
    calcenergy total
    calcforce no
    write pot dx pot_${level}m
    usemap diel $i
    usemap kappa $i
    usemap charge $i
end
}


# Template for generate input file for mbuild
set mbuilder_template {dielx_${level}.dx
$pore_toroidal_r0
$pore_toroidal_k0
$par_shape_thick
$pore_cylindrical_r
$pore_cylindrical_r
[expr $dim_memb_x0-double($dim_memb_x)/2]
[expr $dim_memb_x0+double($dim_memb_x)/2]
[expr $dim_memb_y0-double($dim_memb_y)/2]
[expr $dim_memb_y0+double($dim_memb_y)/2]
[expr $dim_memb_z0-double($dim_memb_z)/2]
[expr $dim_memb_z0+double($dim_memb_z)/2]
$integration_cutoff($i) 
$integration_cyc($i)
$par_diel_memb
$par_diel_head
$par_diel_water
$par_shape_width
$par_diel_solute
$pore_toroidal_ih
[genlayers]
$par_shape_boundary 
$par_temperature
$par_shape_width
$solute_ions_conc
$solute_ions_table(0,charge)
$par_shape_area
[expr -$par_shape_excess]
}









# input Generating
proc genAPBSDummyInp {} {
    set inp "read\n[loadPQR]\nend\n"
    append inp [dodummy]
    return $inp
}
proc genAPBSCalcInp {} {
    set inp "read\n[loadPQR]\n[loadDx]\nend\n"
    append inp [docalc]
    return $inp
}

#load pqr
proc loadPQR { } {
    global solute_protein_pqr
    if ![file exists $solute_protein_pqr] {
	set dummy "ATOM      1 POLO POL     1    0.00000 0.00000  0.00000  0.0  0.00"
	set dummyf [open dummy.pqr w]
	puts $dummyf $dummy
	close $dummyf
	puts "Warning! No molecule file $solute_protein_pqr found!\nUse dummy.pqr"
	set solute_protein_pqr dummy.pqr
    }
    set template "# Read molecule $solute_protein_pqr\n mol pqr $solute_protein_pqr\n"
    return $template 
}

# load Dx 
proc loadDx {} {
    global dim_focus_systems
    set dx "# read diel dx\n"
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {incr i} {
	set level $dim_focus_systems($i,label)
	append dx "diel dx dielx_${level}m.dx diely_${level}m.dx dielz_${level}m.dx\n"
    }
    append dx "\n# read kappa dx\n"
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {incr i} {
	set level $dim_focus_systems($i,label)
	append dx "kappa dx kappa_${level}m.dx\n"
    }
    append dx "\n# read charge dx\n"
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {incr i} {
	set level $dim_focus_systems($i,label)
	append dx "charge dx charge_${level}m.dx\n"
    }
    return $dx
}
#get ion
proc getIonsDef {} {
    global solute_ions_table
    set ions "# ions \n"
    for {set i 0} {$i <[llength $solute_ions_table(rows)]} {incr i} {
	append ions "#ion $solute_ions_table($i,name)\n"
	append ions "ion\ncharge $solute_ions_table($i,charge)\n"
	append ions "conc $solute_ions_table($i,conc)\n"
	append ions "radius $solute_ions_table($i,radius)\n"
    }
    return $ions
}

proc dodummy {} {
    global par_diel_solute par_diel_water par_temperature
    global dim_focus_systems dim_memb_x0 dim_memb_y0 dim_memb_z0
    global dummy_template solute_protein_pqr
    set do ""
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {incr i} {
	set level $dim_focus_systems($i,label)
	append do "# Building $dim_focus_systems($i,label) system\n "
	set x0 $dim_memb_x0
	set y0 $dim_memb_y0
	set z0 $dim_memb_z0
	set nx $dim_focus_systems($i,nx)
	set ny $dim_focus_systems($i,ny)
	set nz $dim_focus_systems($i,nz)
	set sizex $dim_focus_systems($i,x)
	set sizey $dim_focus_systems($i,y)
	set sizez $dim_focus_systems($i,z)
	append do [subst $dummy_template]
    }
    return $do
}


proc genMbuildInp {} {
    global pore_toroidal_r0 pore_toroidal_k0 pore_cylindrical_r pore_toroidal_ih
    global par_shape_thick par_shape_boundary par_shape_width
    global par_charge_anfr par_shape_area par_shape_excess
    global par_diel_memb par_diel_water par_diel_solute par_diel_head
    global solute_ions_conc solute_ions_table
    global dim_focus_systems dim_memb_x0 dim_memb_y0 dim_memb_z0 dim_memb_x dim_memb_y dim_memb_z
    global mbuilder_template par_temperature
    array set integration_cyc { 0 9 1 9 2 6 3 3 4 3}
    array set integration_cutoff { 0 30 1 20 2 20 3 20 4 20 }
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {incr i} {
	set level $dim_focus_systems($i,label)
	set f [open $level.inp w]
	puts -nonewline $f [subst $mbuilder_template]
	close $f
    }
    
}

proc genlayers {} {
    global par_charge_layers par_shape_area 
    set n [llength $par_charge_layers(rows)]
    set layers "$n\n"
    for {set i 0 } { $i < $n } {incr i} {
	append layers "[expr $par_charge_layers($i,density)*$par_shape_area]\n"
	append layers "$par_shape_area\n"
	append layers "$par_charge_layers($i,loc)\n"
	append layers "$par_charge_layers($i,width)\n"
    }
    set layers [string range $layers 0 end-1]
    return $layers
}

proc docalc { } {
    global par_diel_solute par_diel_water par_temperature
    global dim_focus_systems dim_memb_x0 dim_memb_y0 dim_memb_z0
    global calc_template solute_protein_pqr
    set do ""
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {} {
	set level $dim_focus_systems($i,label)
	append do "# Building $dim_focus_systems($i,label) system\n "
	set x0 $dim_memb_x0
	set y0 $dim_memb_y0
	set z0 $dim_memb_z0
	set nx $dim_focus_systems($i,nx)
	set ny $dim_focus_systems($i,ny)
	set nz $dim_focus_systems($i,nz)
	set sizex $dim_focus_systems($i,x)
	set sizey $dim_focus_systems($i,y)
	set sizez $dim_focus_systems($i,z)
	if { $i == 0} { set boundary mdh } else {set boundary focus}
	incr i
	append do [subst $calc_template]
    }
    return $do

}


#Job management
set Joblist {}
set Running {}

proc genJob {} {
    global dim_focus_systems bin_mbuild_path bin_apbs_path Progress
    
    set f [open gen.in w]
    puts $f [genAPBSDummyInp]
    close $f
    set checkpoint 1
    set job ""
    set job "$bin_apbs_path gen.in\n"
    append job "#checkpoint $checkpoint\n"
    incr checkpoint
    genMbuildInp
    for {set i 0} { $i <[llength $dim_focus_systems(rows)] } {incr i} {
	set level $dim_focus_systems($i,label)
	append job "$bin_mbuild_path <$level.inp\n"
	append job "#checkpoint $checkpoint\n"
	incr checkpoint		
    }
    set f [open calc.in w]
    puts $f [genAPBSCalcInp]
    close $f
    append job "$bin_apbs_path calc.in \n"
    append job "#checkpoint $checkpoint\n"
    append job "echo success"
    return [list job $job checkpoints $checkpoint]
}



proc AddJob { jobs } {
    global Progress Joblist Progess Path workDir
    set mother [thread::id]
    #initiate thread 
    set id [thread::create { proc doJob {  mother dir jobs} {
	set id [thread::id]
    set result "failed"
	set jobs [dict get $jobs job]
	thread::send $mother "set Progress($id) 0"
        foreach line [split $jobs \n] {
	if [regexp -- {#checkpoint\s+(\d+)} $line matched checkpoint] {
		thread::send $mother "set Progress($id) $checkpoint"
		continue
	}
	set time [clock format [clock clicks] -format %H:%M:%S]
	thread::send $mother "puts \"$id \\\[$time\\\] Do> $line\""
	cd $dir 
	catch { eval "exec $line"} result
	set result [ string map {\" \\\"} $result ]
	set time [clock format [clock clicks] -format %H:%M:%S]
	foreach line [split $result \n] {
	thread::send $mother "puts \"$id \\\[$time\\\] Result> $line\""
	}

     }
     #thread::exit
     return 
    }
    thread::wait
    }]
    #start thread 
    lappend Joblist $id

    thread::errorproc reportError
    # add thread informtion
    global Progess Path Jobcontent
    set Progress($id) 0
    set Path($id) $workDir
    set Jobcontent($id) $jobs 
    # add thread into list 
    global Title Type jobs_list_jobstatus Format 
    dict set jobs_list_jobstatus $id { path status }
    set Title(jobs.list.jobstatus.$id.path) ""
    set Type(jobs.list.jobstatus.$id.path) Status
    upvar #0 jobs_list_jobstatus_${id}_path path
    set path Path($id)
    set Title(jobs.list.jobstatus.$id.status) ""
    set Type(jobs.list.jobstatus.$id.status) Status(Progress:7)
    set Format(jobs.list.jobstatus.$id.status) {width 70 style dots }
    upvar #0 jobs_list_jobstatus_${id}_status status 
    set status Progress($id)
    refreshField jobs.list.jobstatus
    refreshJobs
    return $id
}
proc reportError {id info} {
   global Title Type jobs_list_jobstatus Format 
   if {$id == [thread::id]} { error $info }
   set Title(jobs.list.jobstats.$id.status) "" 
   set Type(jobs.list.jobstatus.$id.status) Status(Error)
   set Description(jobs.list.jobstatus.$id.status) $info
   foreach line [split $info \n] {
     puts "$id ERROR> $line"
   }
   refreshField jobs.list.jobstatus
}

proc refreshJobs {} {
    global Joblist jobs_control_maxrunning Running Progress Path Jobcontent Title Type
    #remove finished job from Running 
    foreach job $Running {
        if { $Progress($job) >= 7 } { 
          # Job finished, remove job from running
          thread::release $job
          set Running [lremove $Running $job]
          set Title(jobs.list.jobstats.$job.status) "" 
          set Type(jobs.list.jobstatus.$job.status) Status(Completed)
        }
    }
    set openSlot [expr ${jobs_control_maxrunning} - [llength Running]]
    
    # send pending job to run list if there is/are slot(s) open.
    foreach pending $Joblist {
       #until no openSlot 
       if { $openSlot <= 0} break 
       # remove pending job from Joblist and add to running, send the job to thread.
        set Joblist [lremove $Running $pending]
        lappend Running $pending 
        thread::send -async $pending " doJob [thread::id] $Path($pending) \"$Jobcontent($pending)\""
        incr openSlot -1
    }
}
proc DelJob {} {
    global Selection jobs_list_jobstatus  
    #foreach job that is selected, stop it and delete it from list
    foreach name [array names Selection] {
     if {$Selection($name)} {
        set id [lindex [split $name .] end]
        if [catch {thread::release $id } error ] {
          puts "Warning: Could not delete thread $id\n $error"
        }
        dict unset jobs_list_jobstatus $id 
     }
    }
   refreshField jobs.list.jobstatus
}



# Helper & unfinished.
proc lremove { list item } {
    set idx [lsearch $list $item]
    if {$idx >= 0} { return [lreplace $list $idx $idx]}
}


#proc load {} {}
#proc fillMemb {} {}
#proc calcPotential {} {}
#proc cleanUp {} {}


#test
#genAPBSDummyInp
#genAPBSCalcInp
#genMbuildInp
